//
//  NSData+hash.m
//  helper
//
//  Created by huangyi on 12-10-29.
//  Copyright (c) 2012年 huangyi. All rights reserved.
//

#import "NSData+hash.h"
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonHMAC.h>

static NSData* digest(NSData *data, unsigned char *(*cc_digest)(const void *, CC_LONG, unsigned char *), CC_LONG digestLength)
{
	unsigned char md[digestLength];
    memset(md, 0, sizeof(md));
	cc_digest([data bytes], [data length], md);
	return [NSData dataWithBytes:md length:sizeof(md)];
}

static NSData *hmac(NSData *data,NSString *key,CCHmacAlgorithm algorithm,CC_LONG hmacLength){
    unsigned char md[hmacLength];
    memset(md, 0, sizeof(md));
    CCHmac(kCCHmacAlgSHA1, key.UTF8String, key.length, data.bytes, data.length, md);
    return [NSData dataWithBytes:md length:sizeof(md)];
}

NSData* file_digest_md5(NSString* filePath){
    // Declare needed variables
    NSData *result=nil;
    CFReadStreamRef readStream = NULL;
    // Get the file URL
    CFURLRef fileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,(CFStringRef)filePath,kCFURLPOSIXPathStyle,(Boolean)false);
    if (fileURL){
        // Create and open the read stream
        readStream = CFReadStreamCreateWithFile(kCFAllocatorDefault,(CFURLRef)fileURL);
        if (readStream){
            bool didSucceed = (bool)CFReadStreamOpen(readStream);
            if (didSucceed){
                // Initialize the hash object
                CC_MD5_CTX hashObject;
                CC_MD5_Init(&hashObject);
                // Make sure chunkSizeForReadingData is valid
                size_t chunkSizeForReadingData=4096;
                // Feed the data to the hash object
                bool hasMoreData = true;
                while (hasMoreData) {
                    uint8_t buffer[chunkSizeForReadingData];
                    CFIndex readBytesCount = CFReadStreamRead(readStream,(UInt8 *)buffer,(CFIndex)sizeof(buffer));
                    if (readBytesCount == -1) break;
                    if (readBytesCount == 0) {
                        hasMoreData = false;
                        continue;
                    }
                    CC_MD5_Update(&hashObject,(const void *)buffer,(CC_LONG)readBytesCount);//MD5
                }
                // Check if the read operation succeeded
                didSucceed = !hasMoreData;
                // Abort if the read operation failed
                if (didSucceed){
                    // Compute the hash digest
                    unsigned char digest[CC_MD5_DIGEST_LENGTH];
                    CC_MD5_Final(digest, &hashObject);
                    // Compute the string result
                    result = [NSData dataWithBytes:digest length:sizeof(digest)];
                }
            }
        }
    }
    if (readStream) {
        CFReadStreamClose(readStream);
        CFRelease(readStream);
    }
    if (fileURL) {
        CFRelease(fileURL);
    }
    return result;
}

@implementation NSData (hash)

- (NSData *)md2{
    return digest(self, CC_MD2, CC_MD2_DIGEST_LENGTH);
}

- (NSData *)md4{
    return digest(self, CC_MD4, CC_MD4_DIGEST_LENGTH);
}

- (NSData *)md5{
    return digest(self, CC_MD5, CC_MD5_DIGEST_LENGTH);
}

- (NSData *)sha1{
    return digest(self, CC_SHA1, CC_SHA1_DIGEST_LENGTH);
}

- (NSData *)sha224{
    return digest(self, CC_SHA224, CC_SHA224_DIGEST_LENGTH);
}

- (NSData *)sha256{
    return digest(self, CC_SHA256, CC_SHA256_DIGEST_LENGTH);
}

- (NSData *)sha384{
    return digest(self, CC_SHA384, CC_SHA384_DIGEST_LENGTH);
}

- (NSData *)sha512{
    return digest(self, CC_SHA512, CC_SHA512_DIGEST_LENGTH);
}

-(NSData*)sha1hmac:(NSString*)key{
    return hmac(self, key, kCCHmacAlgSHA1, CC_SHA1_DIGEST_LENGTH);
}
-(NSData*)md5hmac:(NSString*)key{
    return hmac(self, key, kCCHmacAlgMD5, CC_MD5_DIGEST_LENGTH);
}

@end
